package wili.json;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import wili.c.Ca;
import wili.c.Misc9a;
import wili.c.Str9a;

/**
 * @author weila 2022年9月7日
 */
public abstract class JsonNode {// 对象|属性|数组|值。
	public final int id = ++GenID;

	public JsonNode dad;

	/**
	 * 暂存映射值。
	 */
	public Object mapingToValue__;

	public final JsonFile0 file;

	public JsonNode targetNode;

	List<JsonNode> sons;

	final int startInd;

	final int endInd;

	/**
	 * 属性名
	 */
	public String attName = "";// 非根结点必有属性名或下标。

	public String stringValue;

	public int lineNo, lineNo2 = -1;
	{
		if (id == 165)
			Ca.pause();
	}

	public String getString(boolean includeAttName) {
		if (!this.attName.isEmpty() && includeAttName)
			return attName + ":" + file.fileSt.substring(startInd, endInd);
		return file.fileSt.substring(startInd, endInd);
	}

	public static void sort(List<JsonNode> list) {
		Collections.sort(list, new Comparator<JsonNode>() {
			@Override
			public int compare(JsonNode o1, JsonNode o2) {
				return o1.attName.compareTo(o2.attName);
			}
		});
	}

	private String path;
	{
		if (id == 311)
			Ca.pause();
	}

	/**
	 * @return JsonLeaf's stringValue or null.
	 */
	public String stringValue() {
		if (this instanceof JsonLeaf) {
			return toLeaf().stringValue;
		}
		return null;
	}

	public JsonNode setAttName(String att) {
		this.attName = att;
		return this;
	}

	public JsonNode setStringValue(String att) {
		if (att.contains("null"))
			Ca.pause();
		this.stringValue = att;
		return this;
	}

	public JsonLeaf toLeaf() {
		return (JsonLeaf) this;
	}

	public JsonArr toArr() {
		return (JsonArr) this;
	}

	public JsonObj toObj() {
		return (JsonObj) this;
	}

	String getPathName() {// getPathInDad
		if (dad instanceof JsonArr) {// element pathName is it's index in array.
			int ind = this.index();
			return "[" + ind + "]";
		} else if (dad instanceof JsonAtt__) {
			JsonAtt__ att = (JsonAtt__) dad;
			return att.name;// attr's name and value's pathName.
		} else if (dad instanceof JsonObj) {// 属性返空
			Ca.asert(this instanceof JsonAtt__, this.getClass());
			return "";
		} else {
			return this.getString(false);
		}
	}

	public String getPath(int refresh) {
		if (path != null && refresh != 1)
			return path;
		StringBuilder sb = new StringBuilder();
		this.getPath00(sb);
		return this.path = sb.toString();
	}

	private void getPath00(StringBuilder sb) {
		if (dad != null) {
			dad.getPath00(sb);
		}
		int len = sb.length();
		if (this instanceof JsonAtt__) {
			return;// attr has no pathName, att.name is pathName of att's name and value.
		}
		sb.append("/" + this.getPathName());
		this.path = sb.substring(len, sb.length());
	}

	protected JsonNode(String att) {
		this.attName = att;
		this.file = null;
		this.startInd = this.endInd = -1;
	}

	public JsonNode(JsonFile0 file, int start, JsonNode dad) {
		this.file = file;
		final String fileSt = file.fileSt;
		this.startInd = start;
		this.lineNo = Misc9a.lineNoAt(start, file.lineInds);
		if (dad != null)
			dad.addSon(this);
		Ca.log(-1, "Node.start=", start);
		if (start == 41)
			Ca.pause();
		if (this instanceof JsonArr || this instanceof JsonObj) {
			this.endInd = file.indMap.get(start) + 1;
			this.lineNo2 = Misc9a.lineNoAt(endInd - 1, file.lineInds);
		} else if (this instanceof JsonLeaf) {
			char ch = fileSt.charAt(start);
			JsonLeaf va = (JsonLeaf) this;
			if (ch == '"') {
				String st = Str9a.readQuote(fileSt, start);
				va.stringValue = st.substring(1, st.length() - 1);
				this.endInd = start + st.length();
			} else {
				va.stringValue = Str9a.readPat("[^\r\n,]+", fileSt, start);
				this.endInd = start + va.stringValue.length();
				va.value = Str9a.simpleValueOfStr(va.stringValue, va.stringValue);
			}
			if (va.stringValue.equals("asdf0"))
				Ca.pause();
		} else if (this instanceof JsonAtt__) {// "attName" : 对象|数组|串|字面值
			Ca.asert(false, "10-16");
			JsonAtt__ att = (JsonAtt__) this;
			String qt = Str9a.readQuote(fileSt, start);
			att.name = qt.substring(1, qt.length() - 1);
			int ind = start + qt.length();
			String st = Str9a.readPat("\\s*:\\s*", fileSt, ind);
			char ch = fileSt.charAt(ind = ind + st.length());
			if (ch == '{') {
				// 值是对象
				att.valueNode = new JsonObj(file, ind, this);
			} else if (ch == '[') {
				// 值是数组
				att.valueNode = new JsonArr(file, ind, this);
			} else {
				// 值是串或字面值。
				att.valueNode = new JsonLeaf(file, ind, this);
			}
			this.endInd = att.valueNode.endInd;
		} else {
			throw new RuntimeException("never");
		}
	}

	public void addSon(int ind, JsonNode son) {
		if (sons == null)
			sons = new ArrayList<>();
		sons.add(ind, son);
		if (son.id == 18833)
			Ca.pause();//18833  18802
		son.dad = this;
	}

	public void addSon(JsonNode son) {
		this.addSon(sons == null ? 0 : sons.size(), son);
	}

	public void print() {
		StringBuilder sb = new StringBuilder();
		this.print("\n", sb);
		Ca.log(sb.toString());
	}

	public void print(String dent, StringBuilder sb) {
		sb.append(dent + this.toString());
		if (sons == null)
			return;
		dent += "   ";
		for (int i = 0; i < sons.size(); i++) {
			sons.get(i).print(dent, sb);
		}
	}

	public List<JsonNode> findAtts(boolean recur, String name, Object value) {
		List<JsonNode> ret = new ArrayList<>();
		findNodes(recur, new Predicate<JsonNode>() {
			@Override
			public boolean test(JsonNode node) {
				if (name.equals(node.attName)) {
					if (value == null || value.equals(node.stringValue)) {
						ret.add(node);
						return true;
					}
				}
				return false;
			}
		});
		return ret;
	}

	public JsonObj findObjByAtt(String att, String valueSt) {
		List<JsonObj> ret = findObjsByAtt(true, att, valueSt);
		if (ret.size() > 0)
			return ret.get(0);
		return null;
	}

	<T extends JsonNode> List<String> findAttStringValues(String name, String value, Function<T, String> fun) {
		Ca.asert(false, "2022-10-15");
		List<String> ret = this.findStrList(new Predicate<JsonNode>() {
			@Override
			public boolean test(JsonNode t) {
				if (t instanceof JsonAtt__) {
					JsonAtt__ att = (JsonAtt__) t;
					if (att.name.equals(name)) {
						if (value == null || value.equals(att.valueNode.stringValue())) {
							return true;
						}
					}
				}
				return false;
			}
		}, fun);
		return ret;
	}

	<T extends JsonNode> List<String> findStrList(Predicate<JsonNode> test, Function<T, String> fun) {
		List<String> ret = new ArrayList<>();
		Predicate<JsonNode> pred = new Predicate<JsonNode>() {
			@Override
			public boolean test(JsonNode node) {
				if (test.test(node)) {
					String st = fun.apply((T) node);
					ret.add(st);
				}
				return false;
			}
		};
		this.traverse(pred);
		return ret;
	}

	public boolean traverse(Predicate<JsonNode> test) {
		if (test.test(this)) {
			return true;
		}
		if (sons == null)
			return false;
		List<JsonNode> list = new ArrayList<>(sons);
		for (JsonNode son : list) {
			son.traverse(test);
		}
		return false;
	}

	public List<JsonNode> findNodesByName(boolean recursive, String... attNames) {
		List<String> attList = Arrays.asList(attNames);
		List<JsonNode> list = this.findNodes(recursive, new Predicate<JsonNode>() {
			@Override
			public boolean test(JsonNode t) {
				if (attList.contains(t.attName))
					return true;
				return false;
			}
		});
		return list;
	}

	public <T extends JsonNode> List<T> findNodes(boolean recursive, Predicate<JsonNode> pred) {
		List<T> ret = new ArrayList<>();
		findNodes(recursive, 999, ret, pred);
		return ret;
	}

	/**
	 *
	 */
	public boolean containsStrs(boolean and, String... strs) {
		//
		String st = this.getString(true);
		if (and) {// and
			for (String ele : strs) {
				int ind = st.indexOf(ele);
				if (ind == -1)
					return false;
			}
			return true;
		} else {// or
			for (String ele : strs) {
				int ind = st.indexOf(ele);
				if (ind != -1)
					return true;
			}
			return false;
		}
	}

	public List<JsonObj> findObjsByAtt(boolean recursive, String att, String valueSt) {
		List<JsonObj> ret = new ArrayList<>();
		findNodes(true, 9999, ret, new Predicate<JsonNode>() {
			@Override
			public boolean test(JsonNode nod) {
				if (nod instanceof JsonObj) {
					JsonObj obj = (JsonObj) nod;
					JsonNode ret = obj.att(att);
					if (ret != null) {
						if (valueSt == null || valueSt.equals(ret.stringValue()))
							return true;
					}
				}
				return false;
			}
		});
		return ret;
	}

	public List<JsonNode> sons() {
		return sons;
	}

	static int inc;

	private <T extends JsonNode> boolean findNodes(boolean recursive, int maxNum, List<T> toList,
			Predicate<JsonNode> pred) {
		if (++inc == 23)
			Ca.pause();
		Ca.log(-1, inc, ", findNodes: ", this);
		if (inc == 584)
			Ca.pause();
		if (pred.test(this)) {
			toList.add((T) this);
			if (maxNum != -1 && toList.size() >= maxNum)
				return true;
			if (!recursive)
				return false;// 找到后，不再递归深入
		}
		if (this instanceof JsonObj) {
			for (JsonNode son : this.sons) {
				if (son.findNodes(recursive, maxNum, toList, pred))// return true means return to root.
					return true;
			}
		} else if (this instanceof JsonArr) {
			for (JsonNode son : this.sons) {
				if (son.findNodes(recursive, maxNum, toList, pred))
					return true;
			}
		} else if (this instanceof JsonAtt__) {
			Ca.asert(false, this);
			JsonAtt__ att2 = (JsonAtt__) this;
			if (att2.valueNode.findNodes(recursive, maxNum, toList, pred))
				return true;
		} else {
			Ca.asert(this instanceof JsonLeaf, this.getClass());
		}
		return false;
	}

	public <T extends JsonNode> List<T> findNodesByPath(Object... paths) {
		List<T> toList = new ArrayList<>();
		findNodesByPath00(0, toList, paths);
		return toList;
	}

	<T extends JsonNode> void findNodesByPath00(int pathInd, List<T> toList, Object... paths) {
		if (pathInd >= paths.length) {
			toList.add((T) this);
			return;
		}
		Object path = paths[pathInd];
		if (path instanceof Integer) {
			int ind = (Integer) path;
			if (this instanceof JsonArr) {
				if (ind >= sons.size()) {
					Ca.log(1, "Not found path " + pathInd + " at: " + this);
					return;
				}
				JsonNode son = sons.get(ind);
				son.findNodesByPath00(pathInd + 1, toList, paths);
				return;
			} else {
				Ca.log(1, "Mismatch type: " + this.getClass().getSimpleName() + ", at: " + this);
				return;
			}
		} else {
			String attPath = (String) path;
			if (this instanceof JsonObj) {
				JsonObj obj = this.toObj();
				JsonNode son = obj.attMap.get(attPath);
				if (son != null) {
					// Ca.log(1, "Not found attr " + attPath + " at: " + this);
					son.findNodesByPath00(pathInd + 1, toList, paths);
				}
				Collection<JsonNode> atts = obj.attMap.values();
				int ind = -1;
				for (JsonNode att : atts) {
					if (att == son)
						continue;
					if (++ind == 2)
						Ca.pause();
					att.findNodesByPath00(pathInd, toList, paths);
				}
			} else if (this instanceof JsonArr) {
				Collection<JsonNode> elements = this.sons();
				int ind = -1;
				for (JsonNode att : elements) {
					if (++ind == 2)
						Ca.pause();
					att.findNodesByPath00(pathInd, toList, paths);
				}
			} else {
				Ca.asert(sons == null, this);
			}
			return;
		}
	}

	public <T extends JsonNode> T son(Object nameOrIndex, Object value) {
		JsonNode ret = son(nameOrIndex);
		if (ret == null)
			return null;
		if (value == null)
			return (T) ret;
		if (value.equals(ret.stringValue))
			return (T) ret;
		if ("sdf".isEmpty())
			if (ret.matchValue(value))
				return (T) ret;
		return null;
	}

	public String valueOfSon(Object nameOrIndex, String def) {
		JsonNode son = this.son(nameOrIndex);
		if (son == null)
			return def;
		return son.stringValue;
	}

	@SuppressWarnings("unchecked")
	public <T extends JsonNode> T son(Object nameOrIndex) {
		if (this instanceof JsonObj) {
			if (nameOrIndex instanceof String) {
				return (T) this.toObj().attMap.get(nameOrIndex);
			}
			return (T) this.sons.get((Integer) nameOrIndex);
		} else if (this instanceof JsonArr) {
			if (nameOrIndex instanceof String)
				return null;
			return (T) this.toArr().sons.get((Integer) nameOrIndex);
		} else {
			return null;
		}
	}

	public void removeSon(JsonNode son) {
		Ca.asert(sons.remove(son), "");
	}

	public JsonNode removeSon(Object nameOrIndex, Object value) {
		if (this.sons == null)
			return null;
		if (nameOrIndex instanceof JsonNode) {
			int ind = sons.indexOf(nameOrIndex);
			if (ind != -1) {
				if (value == null || sons.get(ind).equals(value)) {
					return sons.get(ind);
				}
			}
			return null;
		} else if (this instanceof JsonObj) {
			JsonObj obj = (JsonObj) this;
			if (value == null) {
				JsonNode attNode = obj.attMap.remove(nameOrIndex);
				if (attNode == null)
					return attNode;
				if (attNode != null) {
					for (int i = 0; i < obj.sons.size(); i++) {
						JsonNode son = obj.son(i);
						if (son.attName.equals(nameOrIndex)) {
							if (value == null || son.equals(value)) {
								return obj.sons.remove(i);
							}
						}
					}
				}
				Ca.asert(false, "\nattNode=" + attNode);
				return null;
			}
		} else if (this instanceof JsonArr) {
			int ind = (Integer) nameOrIndex;
			JsonArr obj = (JsonArr) this;
			return obj.sons.get(ind);
		}
		return null;
	}

	abstract void saveToBuffer(String dent, StringBuilder buf);

	public List<JsonLeaf> findLeafs(String name, Object value, int limit) {
		List<JsonLeaf> ret = new ArrayList<>(limit < 0 ? 16 : limit);
		try {
			this.traverse(new Predicate<JsonNode>() {
				@Override
				public boolean test(JsonNode t) {
					if (t instanceof JsonLeaf) {
						JsonLeaf leaf = (JsonLeaf) t;
						if (!leaf.attName.equals(name))
							return false;
						if (value == null || leaf.matchValue(value)) {
							ret.add((JsonLeaf) t);
							if (limit > 0 && limit >= ret.size()) {
								throw new RuntimeException("limit");
							}
						}
					}
					return false;
				}
			});
			return ret;
		} catch (RuntimeException e) {
			if (e.getMessage().equals("limit")) {
				return ret;
			}
			throw e;
		}
	}

	static String quote2(String st) {
		return '"' + st + '"';
	}

	/**
	 * @return 元素在（对象属性集|数组）中的下标。
	 */
	public int index() {
		if (dad == null)
			return -1;
		int ind = dad.sons.indexOf(this);
		return ind;
	}

	public <T extends JsonNode> T replaceBy(T node) {
		int ind = this.index();
		if (dad instanceof JsonObj) {
			// Object va = ((JsonObj) dad).attMap.remove(this.attName);
			Object va = ((JsonObj) dad).attMap.put(this.attName, node);
			Ca.asert(va != null, this.toString());
		}
		this.sons = null;
		this.setAttName("REMOVED att.").setStringValue("REMOVED value.");
		this.dad.sons.set(ind, node);
		node.dad = dad;
		return node;
	}

	public void renameTo(String newName) {
		if (!this.attName.equals(newName)) {
			if (dad instanceof JsonObj) {
				dad.toObj().attMap.remove(this.attName);
				dad.toObj().attMap.put(newName, this);
			}
			this.setAttName(newName);
		}
	}

	public static List<JsonNode> dadListOfNodes(List<JsonNode> list) {
		return list.stream().map(nd -> nd.dad).collect(Collectors.toList());
	}

	public static List<String> valueListOfNodeList(Object attName, List<JsonNode> list) {
		return list.stream().map(nd -> nd.dad.son(attName).stringValue).collect(Collectors.toList());
	}

	public boolean matchValue(Object value) {
		return false;
	}

	public <T extends JsonNode> T att(String att) {
		Ca.asert(false, "todo.");
		return null;
	}

	public <T extends JsonNode> T findAncestor(String name) {
		JsonNode dad = this;
		while (dad != null) {
			if (dad.getPathName().equals(name)) {
				return (T) dad;
			}
			dad = this.dad;
		}
		return null;
	}

	public JsonNode findAttFromAncestorOrSelf(String name, String value) {
		JsonNode ret = this.son(name, value);
		if (ret == null)
			return ret;
		return this.findAttFromAncestor(name, value);
	}

	/**
	 * @return ancestor with given name.
	 */
	public JsonNode findAttFromAncestor(String name, String value) {
		if (dad == null)
			return null;
		JsonNode ret = dad.son(name, value);
		if (ret == null) {
			ret = dad.findAttFromAncestor(name, value);
		}
		return ret;
	}

	public void sortSons(List<String> orders) {
		Map<String, Integer> map = new HashMap<>();
		for (int i = 0; i < orders.size(); i++) {
			map.put(orders.get(i), i);
		}
		Collections.sort(sons, new Comparator<JsonNode>() {
			@Override
			public int compare(JsonNode o1, JsonNode o2) {
				Integer ind = map.get(o1.attName);
				if (ind == null)
					ind = o1.index();
				return ind.compareTo(ind);
			}
		});
	}

	public Map<String, List<JsonNode>> findNodeMap_mathPath(Map<String, List<JsonNode>> toMap, String idAtt,
			String... path) {
		if (toMap == null)
			toMap = new HashMap<>();
		List<JsonNode> list = this.findNodesByPath(path);
		for (JsonNode ele : list) {
			JsonNode id = ele.findAttFromAncestor(idAtt, null);
			List<JsonNode> lis = toMap.get(id.stringValue);
			if (lis == null) {
				toMap.put(id.stringValue, lis = new ArrayList<>(5));
			}
			lis.add(ele);
		}
		return toMap;
	}

	static int GenID;
}
